import copy
import datetime
import time
from decimal import Decimal, ROUND_DOWN
from typing import *

from loguru import logger

from globalVar import GlobalVar
from utils.error import TradeError

from utils.exchange.baseExchange import BaseExchange
from utils.exchange.fakeExchange import FakeExchange
from utils.exchange.binance import BinanceBase
from config import SPLIT_AMOUNT, SPLIT_ORDER


class BaseTradeType:
    """
    交易对象
    """
    reshape_amount: Decimal = SPLIT_AMOUNT

    def __init__(self):
        self.exchange: Optional[BaseExchange] = None

    def spot_trade(self, orders: List[Dict[str, Any]]) -> List[Tuple[bool, Any]]:
        spot_info = self.exchange.spot_exchange_info
        items: List[Dict[str, Any]] = []
        resp: List[Any] = []
        for order in orders:
            order_info = copy.deepcopy(order)
            type_: str = order['type']
            symbol: str = order['symbol']
            side: str = order['side']
            quantity: Union[Decimal, float, int] = order['quantity']
            # 拆单
            cum_qty = quantity * GlobalVar.SPOT_PRICE[symbol]
            if SPLIT_ORDER and cum_qty > self.reshape_amount:
                times: Decimal = cum_qty // self.reshape_amount
                split_orders: List[Dict[str, Any]] = []
                for i in range(int(times)):
                    _order = copy.deepcopy(order)
                    _order['quantity'] = self.reshape_amount / GlobalVar.SPOT_PRICE[symbol]
                    split_orders.append(_order)
                _order = copy.deepcopy(order)
                _order['quantity'] = cum_qty % self.reshape_amount / GlobalVar.SPOT_PRICE[symbol]
                split_orders.append(_order)
                resp.append(self.spot_trade(split_orders))
                continue

            quantity: Decimal = Decimal(abs(quantity)).quantize(spot_info[symbol]['minQty'], rounding=ROUND_DOWN)
            if order.get('price') is not None:
                price = Decimal(order['price']).quantize(spot_info[symbol]['tickSize'], rounding=ROUND_DOWN)
            else:
                price = GlobalVar.SPOT_PRICE[symbol]

            if price * quantity < spot_info[symbol]['minNotional']:
                print(f'{datetime.datetime.fromtimestamp(GlobalVar.EXE_TIMESTAMP, datetime.timezone.utc)}: '
                      f'spot下单量不足，{symbol}，数量：{quantity}，价格：{price}')
                continue
            order_info.update({'type': type_.upper(), 'symbol': symbol.upper(), 'quantity': quantity,
                               'side': side.upper()})
            if type_.lower() == 'limit':
                order_info['price'] = price
            items.append(order_info)
        if len(items) == 0:
            return []
        resp += self.exchange.spot_trade(items)
        return resp

    def swap_trade(self, orders: List[Dict[str, Any]]) -> List[Tuple[bool, Any]]:
        swap_info = self.exchange.swap_exchange_info
        items: List[Dict[str, Any]] = []
        resp = []
        for order in orders:
            order_info = copy.deepcopy(order)
            type_: str = order['type']
            symbol: str = order['symbol']
            side: str = order['side']
            pos_side: str = order.get('positionSide', 'BOTH')
            quantity: Union[Decimal, float, int] = order['quantity']
            # 拆单
            cum_qty = quantity * GlobalVar.SWAP_PRICE[symbol]
            if SPLIT_ORDER and cum_qty > self.reshape_amount:
                times: Decimal = cum_qty // self.reshape_amount
                split_orders: List[Dict[str, Any]] = []
                for i in range(int(times)):
                    _order = copy.deepcopy(order)
                    _order['quantity'] = self.reshape_amount / GlobalVar.SWAP_PRICE[symbol]
                    split_orders.append(_order)
                _order = copy.deepcopy(order)
                _order['quantity'] = cum_qty % self.reshape_amount / GlobalVar.SWAP_PRICE[symbol]
                split_orders.append(_order)
                resp += self.swap_trade(split_orders)
                continue
            quantity: Decimal = Decimal(abs(quantity)).quantize(swap_info[symbol]['minQty'], rounding=ROUND_DOWN)
            reduce_only: bool = order.get('reduceOnly')
            if order.get('price') is not None:
                price = Decimal(order['price']).quantize(swap_info[symbol]['tickSize'], rounding=ROUND_DOWN)
            else:
                price = GlobalVar.SWAP_PRICE[symbol]

            if price * quantity < swap_info[symbol]['minNotional']:
                print(f'{datetime.datetime.fromtimestamp(GlobalVar.EXE_TIMESTAMP, datetime.timezone.utc)}: '
                      f'swap下单量不足，{symbol}，数量：{quantity}，价格：{price}')
                continue
            order_info.update({'type': type_.upper(), 'quantity': quantity,
                               'positionSide': pos_side.upper(), 'side': side.upper()})
            if type_.lower() == 'limit':
                order_info['price'] = price
            items.append(order_info)
        if len(items) == 0:
            return []
        resp += self.exchange.swap_trade(items)
        return resp

    def transfer(self, asset: str, amount: Decimal, type_: str) -> bool:
        if type_ not in ['MAIN_UMFUTURE', 'UMFUTURE_MAIN']:
            raise TradeError(f'划转类型错误:{type_}')
        if amount == 0:
            return True

        return self.exchange.transfer([{'asset': asset, 'amount': amount, 'type': type_}])[0]

    def margin_type(self, symbols: List[str], type_: str):
        return self.exchange.margin_type(symbols, type_)

    def position_side(self, update_to: bool):
        if self.exchange.swap_position_side() != update_to:
            return self.exchange.swap_position_side(update_to=update_to)

    def swap_leverage(self, symbols: Dict[str, int]):
        return self.exchange.swap_leverage(list(symbols.values()), list(symbols.keys()))

    def dust_asset(self, value: int = 5):
        """
        将不足5美金的现货转换为BNB
        :return:
        """
        assets = self.exchange.spot_balance()
        self.exchange.convert_asset([k for k, v in assets.items() if v < value])


class FakeTradeType(BaseTradeType):
    def __init__(self, uid):
        super().__init__()
        self.exchange = FakeExchange(uid)


class ActualTrader(BaseTradeType):
    """
    交易对象
    """

    def __init__(self, api_key: str, secret_key: str):
        super().__init__()
        self.api_key = api_key
        self.secret_key = secret_key
        self.exchange = BinanceBase(api_key, secret_key)

    def check_permission(self) -> Tuple[bool, str]:
        """
        检验apiKey权限
        :return:
        """
        permission: dict = self.exchange.key_permission
        if not permission.get('enableFutures', False):
            return False, '缺少合约权限'
        if not permission.get('enableSpotAndMarginTrading', False):
            return False, '缺少交易权限'
        if not permission.get('permitsUniversalTransfer', False):
            return False, '缺少划转权限'
        return True, ''

    def swap_trade(self, orders: List[Dict[str, Any]]) -> List[Tuple[bool, Any]]:
        resp = super().swap_trade(orders)
        for res in resp:
            if not res[0]:
                logger.warning(f'合约交易失败：{res[1]}')
        return resp

    def spot_trade(self, orders: List[Dict[str, Any]]) -> List[Tuple[bool, Any]]:
        resp = super().swap_trade(orders)
        for res in resp:
            if not res[0]:
                logger.warning(f'现货交易失败：{res[1]}')
        return resp

    def extract_asset(self, amount: Decimal, asset: str = 'USDT') -> bool:
        """
        从资金账户提取资金
        :param amount: 资金量
        :param asset: 币种
        :return:
        """
        resp = self.exchange.transfer([{'type': 'FUNDING_MAIN', 'asset': asset, 'amount': amount}])[0]
        return resp[0]
